package it.unica.informatica.enotes.capture;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Camera;
import android.hardware.SensorManager;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.ImageButton;
import android.widget.Chronometer;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;

import it.unica.informatica.enotes.R;
import it.unica.informatica.enotes.manager.AttachmentsAcquisitionManager;
import it.unica.informatica.enotes.manager.AttachmentsStoringManager;

/**
 * This class manage all the objects necessary to take an image or a video from the camera/videocamera
 * 
 * @author Mirko Marras 
 */
public class ImageVideoCapture extends Activity {

   /*********************************************************************************************
    *                                 PARAMETERS OF THE ACTIVITY                                *
    *********************************************************************************************/
   public static final String EXTRA_OUTPUT = "path_to_save";
   public static final String EXTRA_ACTION = "action_to_perform";
   public static final int IMAGE_CAPTURE = 1;
   public static final int VIDEO_CAPTURE = 2;
   public static final int NO_ACTION = 0;

   /*********************************************************************************************
    *                                      STATE VARIABLES                                      *
    *********************************************************************************************/
   
   private boolean isRecording = false;
   private boolean isCaptured = false;
   
   public MediaRecorder mrec = null;
   private Camera mCamera = null;
   private CameraPreview mPreview = null;
   
   private static String mFileName = null;
   private static int captureDrawable = 0;
   private static int requestAction = NO_ACTION;
   
   /*********************************************************************************************
    *                         VARIABLES TO MANAGE THE SCREEN ORIENTATION                        *
    *********************************************************************************************/
   
   private OrientationEventListener mOrientationEventListener;
   private int mOrientation =  -1;

   private static final int ORIENTATION_PORTRAIT_NORMAL =  1;
   private static final int ORIENTATION_PORTRAIT_INVERTED =  2;
   private static final int ORIENTATION_LANDSCAPE_NORMAL =  3;
   private static final int ORIENTATION_LANDSCAPE_INVERTED =  4;
   
   /*********************************************************************************************
    *                                         VIEW OBJECTS                                      *
    *********************************************************************************************/
   
   public FrameLayout mCameraPreview = null;
   
   public ImageView mCaptureView = null;
   public ImageView mPlayView = null;
   
   public ImageButton mRecordImageButton = null;
   public ImageButton mSelectImageButton = null;
   public ImageButton mDiscardImageButton = null;

   public Chronometer myChronometer = null;

   /*********************************************************************************************
    *                               METHODS OF THE ACTIVITY LIFECYCLE                           *
    *********************************************************************************************/

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      
      /* Set the activity to full screen */
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
      setContentView(R.layout.activity_image_video_capture);
      
      /* Get the request type and the path of the file to which store the voice recorded */
      Intent intent = getIntent();
      mFileName = intent.getStringExtra(EXTRA_OUTPUT);
      requestAction = intent.getIntExtra(EXTRA_ACTION, NO_ACTION);
      captureDrawable = (requestAction == IMAGE_CAPTURE) ? R.drawable.ic_action_capture : R.drawable.ic_action_recording;

      /* Initialize view object from layout */
      initializeViews();
   }
   
   @Override
   protected void onResume() {
      super.onResume();
      setOrientationVariablesOnChange();
   }

   @Override
   protected void onStart() {
      super.onStart();

      /* Create an instance of Camera */
      mCamera = getCameraInstance();

      /* Create our Preview view and set it as the content of our activity */
      mPreview = new CameraPreview(this, mCamera);
      FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
      preview.addView(mPreview);
   }

   @Override
   protected void onPause() {
      super.onPause();

      /* Disable the orientation listener */
      mOrientationEventListener.disable();

      /* Stop the video if it is in RECORDING state */
      stopVideoRecorder();
   }

   @Override
   public void onBackPressed() {
      stopVideoRecorder();
      File file = new File(mFileName);
      if (file.exists()) {
         file.delete();
      }
      finish();
   }

   /*********************************************************************************************
    *                          METHODS FOR THE OBJECTS INITIALIZATION                           *
    *********************************************************************************************/

   /**
    * Set all the objects variables to the layout views
    */
   private void initializeViews(){
      /* Retrieve the chronometer */
      myChronometer = (Chronometer) findViewById(R.id.chronometer);

      /* Retrieve all the object of the center of the layout */
      mCaptureView = (ImageView) findViewById(R.id.camera_capture_view);
      mPlayView = (ImageView) findViewById(R.id.play_video);
      mCameraPreview = (FrameLayout) findViewById(R.id.camera_preview);

      /* Retrieve the button used to start/end the recording */
      mRecordImageButton = (ImageButton) findViewById(R.id.buttonRecording);
      mRecordImageButton.setImageDrawable(getResources().getDrawable(captureDrawable));

      /* Set all the needs for the video recording */
      if(requestAction == VIDEO_CAPTURE) {
         /* Start recording video when click on the button */
         mRecordImageButton.setOnClickListener(
                 new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                       if(isRecording) {
                          stopVideoRecorder();
                       } else {
                          startVideoRecorder();
                       }
                    }
                 }
         );
      }
      /* Set all the needs for the image capturing */
      else {
         /* Hide the chronometer */
         myChronometer.setVisibility(View.GONE);

         /* Capture an image when click on the button */
         mRecordImageButton.setOnClickListener(
                 new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                       // get an image from the camera
                       mCamera.takePicture(null, null, new Camera.PictureCallback() {

                          @Override
                          public void onPictureTaken(byte[] data, Camera camera) {
                             isCaptured = true;

                             /* Get the file where the image is saved */
                             File pictureFile = getOutputMediaFile();
                             if (pictureFile == null){
                                return;
                             }

                             /* Decode the image captured and rotate it based on the screen orientation */
                             Bitmap image = BitmapFactory.decodeByteArray(data, 0, data.length);
                             Matrix matrix = new Matrix();
                             try {
                                switch (mOrientation) {
                                   case ORIENTATION_PORTRAIT_NORMAL:
                                      matrix.postRotate(90);
                                      break;
                                   case ORIENTATION_LANDSCAPE_NORMAL:
                                      matrix.postRotate(0);
                                      break;
                                   case ORIENTATION_PORTRAIT_INVERTED:
                                      matrix.postRotate(270);
                                      break;
                                   case ORIENTATION_LANDSCAPE_INVERTED:
                                      matrix.postRotate(180);
                                      break;
                                }

                                /* Create a new bitmap from the original using the matrix to transform the result */
                                Bitmap rotatedBitmap = Bitmap.createBitmap(image , 0, 0, image.getWidth(), image.getHeight(), matrix, true);
                                FileOutputStream fos = new FileOutputStream(pictureFile);
                                rotatedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);

                             } catch (FileNotFoundException e) {
                                e.printStackTrace();
                             } catch (IOException e) {
                                e.printStackTrace();
                             }

                             /* Rotated the preview based on the screen orientation */
                             switch (mOrientation) {
                                case ORIENTATION_PORTRAIT_NORMAL:
                                   getRotatedPreview(270);
                                   break;
                                case ORIENTATION_LANDSCAPE_NORMAL:
                                   getRotatedPreview(0);
                                   break;
                                case ORIENTATION_PORTRAIT_INVERTED:
                                   getRotatedPreview(90);
                                   break;
                                case ORIENTATION_LANDSCAPE_INVERTED:
                                   getRotatedPreview(180);
                                   break;
                             }

                             /* Show the image captured */
                             mCameraPreview.setVisibility(View.GONE);
                             mCaptureView.setVisibility(View.VISIBLE);
                          }
                       });

                       /* Show buttons to save/discard the image */
                       mRecordImageButton.setVisibility(View.GONE);
                       mSelectImageButton.setVisibility(View.VISIBLE);
                       mDiscardImageButton.setVisibility(View.VISIBLE);
                    }
                 }
         );
      }

      /* Retrieve the button to pick the image/video captured */
      mSelectImageButton = (ImageButton) findViewById(R.id.buttonSelect);
      mSelectImageButton.setOnClickListener(new View.OnClickListener() {
         public void onClick(View v) {
            Intent intent = new Intent();
            setResult(RESULT_OK, intent);
            finish();
         }
      });

      /* Retrieve the button to discard the image/video captured */
      mDiscardImageButton = (ImageButton) findViewById(R.id.buttonDiscard);
      mDiscardImageButton.setOnClickListener(new View.OnClickListener() {
         public void onClick(View v) {
            File file = new File(mFileName);
            if (file.exists()) {
               file.delete();
            }
            finish();
         }
      });
   }

   /*********************************************************************************************
    *                          METHODS TO MANAGE THE SCREEN ORIENTATION                          *
    *********************************************************************************************/

   /**
    * Set all the state variables referred to the orientation when the orientation changed
    */
   private void setOrientationVariablesOnChange() {
      /* Set orientation state variables of the screen to rotate ImageButtons and other needs */
      if (mOrientationEventListener == null) {
         mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {

            @Override
            public void onOrientationChanged(int orientation) {
               /* Determine the orientation based on sensor response */
               int lastOrientation = mOrientation;

               if (orientation >= 315 || orientation < 45) {
                  if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) {
                     mOrientation = ORIENTATION_PORTRAIT_NORMAL;
                  }
               }
               else if (orientation < 315 && orientation >= 225) {
                  if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) {
                     mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
                  }
               }
               else if (orientation < 225 && orientation >= 135) {
                  if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) {
                     mOrientation = ORIENTATION_PORTRAIT_INVERTED;
                  }
               }
               else { // orientation <135 && orientation > 45
                  if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) {
                     mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
                  }
               }

               if (lastOrientation != mOrientation) {
                  changeRotation(mOrientation, lastOrientation);
               }
            }
         };
      }

      if (mOrientationEventListener.canDetectOrientation()) {
         mOrientationEventListener.enable();
      }
   }

   /**
    * Rotate all the necessary view objects based on the passed degrees
    * @param degrees The degress to which rotate the objects
    */
   private void rotateAllObjects(int degrees) {
      /* Rotate recording button */
      if(!isRecording) {
         mRecordImageButton.setImageDrawable(getRotatedImage(captureDrawable, degrees));
      }
      else {
         mRecordImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_during_recording, degrees));
      }

      /* Rotate save, discard and play button */
      mSelectImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_yes, degrees));
      mDiscardImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_no, degrees));
      mPlayView.setImageDrawable(getRotatedImage(R.drawable.ic_action_play_over_video, degrees));

      /* Rotate the chronometer */
      AnimationSet animSet = new AnimationSet(true);
      animSet.setInterpolator(new DecelerateInterpolator());
      animSet.setFillAfter(true);
      animSet.setFillEnabled(true);
      final RotateAnimation animRotate = new RotateAnimation(  0.0f,
                                                               (float)degrees,
                                                               RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                                                               RotateAnimation.RELATIVE_TO_SELF, 0.5f);

      animRotate.setDuration(1500);
      animRotate.setFillAfter(true);
      animSet.addAnimation(animRotate);

      myChronometer.startAnimation(animSet);
   }
   
   /**
    * Performs required action to accommodate new orientation
    * @param orientation The new orientation reached
    * @param lastOrientation The last orientation prior the new 
    */
   private void changeRotation(int orientation, int lastOrientation) {
      
      /* Perform the necessary adapts for each orientation */
      switch (orientation) {
         case ORIENTATION_PORTRAIT_NORMAL:
            rotateAllObjects(270);
            break;
         case ORIENTATION_LANDSCAPE_NORMAL:
            rotateAllObjects(0);
            break;
         case ORIENTATION_PORTRAIT_INVERTED:
            rotateAllObjects(90);
            break;
         case ORIENTATION_LANDSCAPE_INVERTED:
            rotateAllObjects(180);
            break;
      }
   }

   /**
    * Rotate a drawable based on the passed degrees
    * @param drawableId The drawable Id to rotate
    * @param degrees The rotate drawable by degrees
    * @return The rotated drawable
    */
   private Drawable getRotatedImage(int drawableId, int degrees) {
      Bitmap original = BitmapFactory.decodeResource(getResources(), drawableId);
      
      Matrix matrix = new Matrix();
      matrix.postRotate(degrees);
      Bitmap rotated = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true);
      
      return new BitmapDrawable(rotated);
   }

   /**
    * Rotate a preview based on the passed degrees
    * @param degrees The rotate image by degrees
    */
   private void getRotatedPreview(int degrees) {
      Bitmap original = null;

      if(requestAction == IMAGE_CAPTURE && isCaptured) {
         original = BitmapFactory.decodeFile(mFileName);
      }
      else if(requestAction == VIDEO_CAPTURE && isCaptured) {
         original = ThumbnailUtils.createVideoThumbnail(mFileName, MediaStore.Video.Thumbnails.FULL_SCREEN_KIND);
      }

      final Matrix matrix = new Matrix();
      matrix.postRotate(degrees);

      int finalHeight = mCameraPreview.getMeasuredHeight();
      int finalWidth = mCameraPreview.getMeasuredWidth();

      Bitmap rotated = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true);
      Bitmap scaled = Bitmap.createScaledBitmap(rotated, finalWidth, finalHeight, true);

      mCaptureView.setImageBitmap(scaled);
   }

   /*********************************************************************************************
    *                             METHODS TO MANAGE THE MEDIA RECORDER                          *
    *********************************************************************************************/

   /**
    * Prepare the Media Recorder for the recording
    * @return True if all the settings are performed, false otherwise
    */
   private boolean prepareVideoRecorder(){
      final int CONV_FACTOR = 1000;

      /* Create camera and Media Recorder */
      mCamera = getCameraInstance();
      mrec = new MediaRecorder();

      /* Unlock and set camera to MediaRecorder */
      mCamera.unlock();
      mrec.setCamera(mCamera);

      /* Set sources */
      mrec.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
      mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);

      /* Set listeners to inform of the limits reached by the video recording */
      mrec.setOnInfoListener(new MediaRecorder.OnInfoListener() {
         @Override
         public void onInfo(MediaRecorder mr, int what, int extra) {
            if (what == android.media.MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
               stopVideoRecorder();
               Toast.makeText(getApplicationContext(), R.string.image_video_capture_max_size_reached, Toast.LENGTH_LONG).show();
            } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
               stopVideoRecorder();
               Toast.makeText(getApplicationContext(), R.string.image_video_capture_max_duration_reached, Toast.LENGTH_LONG).show();
            }
         }
      });

      /* Set resolution, duration, size parameters */
      if(requestAction == VIDEO_CAPTURE) {
         mrec.setMaxDuration((int) AttachmentsAcquisitionManager.ALLOWED_MAX_VIDEO_SECONDS * CONV_FACTOR);
         mrec.setMaxFileSize(AttachmentsAcquisitionManager.ALLOWED_MAX_VIDEO_BYTE + CONV_FACTOR);
      }
      else if(requestAction == IMAGE_CAPTURE) {
         mrec.setMaxFileSize(AttachmentsAcquisitionManager.ALLOWED_MAX_IMAGE_BYTE + CONV_FACTOR);
      }

      mrec.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW));

      /* Adjust the orientation for video recording */
      if(requestAction == VIDEO_CAPTURE) {
         mrec.setOrientationHint(90);
      }

      /* Set output file */
      mrec.setOutputFile(getOutputMediaFile().toString());

      /* Set the preview output */
      mrec.setPreviewDisplay(mPreview.getHolder().getSurface());

      /* Step 6: Prepare configured MediaRecorder */
      try {
         mrec.prepare();
      } catch (IllegalStateException e) {
         e.printStackTrace();
         releaseMediaRecorder();
         return false;
      } catch (IOException e) {
         e.printStackTrace();
         releaseMediaRecorder();
         return false;
      }
      return true;
   }

   /**
    * Start the recording of a video
    */
   private void startVideoRecorder() {
      /* Initialize video camera */
      if (prepareVideoRecorder()) {

         /* Camera is available and unlocked, MediaRecorder is prepared, now you can start recording */
         mrec.start();

         /* Start the chronometer */
         myChronometer.setBase(SystemClock.elapsedRealtime());
         myChronometer.start();
         myChronometer.setTextColor(Color.argb(0xff, 0xff, 0x00, 0x00));

         /* Rotate the recording button based on the screen orientation */
         switch (mOrientation) {
            case ORIENTATION_PORTRAIT_NORMAL:
               mRecordImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_during_recording, 270));
               break;
            case ORIENTATION_LANDSCAPE_NORMAL:
               mRecordImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_during_recording, 0));
               break;
            case ORIENTATION_PORTRAIT_INVERTED:
               mRecordImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_during_recording, 90));
               break;
            case ORIENTATION_LANDSCAPE_INVERTED:
               mRecordImageButton.setImageDrawable(getRotatedImage(R.drawable.ic_action_during_recording, 180));
               break;
         }

         /* Inform that the recording is started */
         isRecording = true;
      } else {
         /* Prepare didn't work, release the camera */
         releaseMediaRecorder();
      }
   }

   /**
    * Stop the recording of the video
    */
   private void stopVideoRecorder() {

     if(isRecording) {
        isCaptured = true;

        /* Stop recording and release camera */
        mrec.stop();
        releaseMediaRecorder();
        mCamera.lock();

        /* Stop the chronometer */
        myChronometer.stop();
        myChronometer.setTextColor(Color.argb(0xff, 0xff, 0xff, 0xff));

        /* Change all the view objects in the layout */
        mRecordImageButton.setImageDrawable(getResources().getDrawable(captureDrawable));

        /* Rotate the preview based on the screen orientation */
        switch (mOrientation) {
           case ORIENTATION_PORTRAIT_NORMAL:
              getRotatedPreview(270);
              rotateAllObjects(270);
              break;
           case ORIENTATION_LANDSCAPE_NORMAL:
              getRotatedPreview(0);
              rotateAllObjects(0);
              break;
           case ORIENTATION_PORTRAIT_INVERTED:
              getRotatedPreview(90);
              rotateAllObjects(90);
              break;
           case ORIENTATION_LANDSCAPE_INVERTED:
              getRotatedPreview(180);
              rotateAllObjects(180);
              break;
        }

        mCameraPreview.setVisibility(View.GONE);
        mCaptureView.setVisibility(View.VISIBLE);
        mPlayView.setVisibility(View.VISIBLE);

        mRecordImageButton.setVisibility(View.GONE);
        mSelectImageButton.setVisibility(View.VISIBLE);
        mDiscardImageButton.setVisibility(View.VISIBLE);

        /* Inform the user that recording has stopped */
        isRecording = false;
     }

     /* If you are using MediaRecorder, release it first */
     releaseMediaRecorder();

     /* Release the camera immediately on pause event */
     releaseCamera();
  }

   /**
    * Release the Media Recorder used to recording the video
    */
   private void releaseMediaRecorder(){
      if (mrec != null) {
         /* Clear recorder configuration */
         mrec.reset();
         /* Release the recorder object */
         mrec.release();
         mrec = null;
         /* Lock camera for later use */
         mCamera.lock();
      }
   }

   /**
    * Release the camera used to recording the video
    */
   private void releaseCamera(){
      if (mCamera != null){
         mPreview.getHolder().removeCallback(mPreview);
         /* Release the camera for other applications */
         mCamera.release();
         mCamera = null;
      }
   }

   /**
    * Get an instance of the camera of the device
    * @return A camera of the device if exists
    */
   private Camera getCameraInstance(){
      releaseCamera();
      Camera c = null;
      try {
         /* Attempt to get a Camera instance */
         c = Camera.open();
         Camera.Parameters params = c.getParameters();

         /* Set focus and flash properly */
         List<String> focusModes = params.getSupportedFocusModes();
         if(requestAction == IMAGE_CAPTURE) {
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
               params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            }
         }
         else if(requestAction == VIDEO_CAPTURE) {
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
               params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            }
         }

         List<String> flashModes = params.getSupportedFlashModes();
         if(requestAction == IMAGE_CAPTURE) {
            if (flashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
               params.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
            }
         }

         c.setParameters(params);
      }
      catch (Exception e){
         Toast.makeText(getApplicationContext(), R.string.image_video_capture_not_available, Toast.LENGTH_LONG).show();
         finish();
      }

      return c;
   }

   /*********************************************************************************************
    *                                   METHODS TO SAVE FILES                                   *
    *********************************************************************************************/

   /**
    * Get the uri of the saved file
    * @return The uri of the saved file
    */
   private static Uri getOutputMediaFileUri(){
      return Uri.fromFile(getOutputMediaFile());
   }

   /**
    * Get the file saved
    * @return Get the file saved
    */
   private static File getOutputMediaFile(){
      return new File(mFileName);
   }

}